package dbinfo

import (
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"sync"
	"time"
	"unicode/utf8"

	Log "github.com/cihub/seelog"
	"gopkg.in/ini.v1"
)

var (
	frameworkFields     map[string]string //框架字段
	frameworkFieldsOnce sync.Once         //单次赋值操作
)

type TableInfo struct {
	DbName  string            `json:"dbName"`
	Name    string            `json:"name" gorm:"column:name; type:varchar"`
	Comment string            `json:"comment" gorm:"column:comment; type:varchar"`
	Fields  map[string]*Field `json:"fields"`
	Columns []*Field          `json:"columns"`
}

type Field struct {
	GsTableName    string `json:"sTableName" gorm:"column:sTableName; type:varchar; DEFAULT:''"`       //字段所在表名
	GsName         string `json:"sName" gorm:"column:sName; type:varchar; DEFAULT:''"`                 //在数据库中的名字段
	GsType         string `json:"sType" gorm:"column:sType; type:varchar; DEFAULT:''"`                 //字段类型
	GiNull         int    `json:"iNull" gorm:"column:iNull; type:int; DEFAULT:1"`                      //是否允许为空
	GiKey          int    `json:"iKey" gorm:"column:iKey; type:int; DEFAULT:2"`                        //是否主键
	GiAuto         int    `json:"iAuto" gorm:"column:iAuto; type:int; DEFAULT:2"`                      //是否自增长
	GiIndex        int    `json:"iIndex" gorm:"column:iIndex; type:int; DEFAULT:99"`                   //序号
	GsDefaultData  string `json:"sDefaultData" gorm:"column:sDefaultData; type:varchar; DEFAULT:''"`   //默认值
	GiMaxLength    int    `json:"iMaxLength" gorm:"column:iMaxLength; type:int; DEFAULT:-1"`           //字段长度
	GiDecimal      int    `json:"iDecimal" gorm:"column:iDecimal; type:int; DEFAULT:2"`                //类型是否有小数
	GiIntLen       int    `json:"iIntLen" gorm:"column:iIntLen; type:int; DEFAULT:0"`                  //整数位的长度
	GiDecimalLen   int    `json:"iDecimalLen" gorm:"column:iDecimalLen; type:int; DEFAULT:0"`          //小数位的长度
	GsComment      string `json:"sComment" gorm:"column:sComment; type:varchar; DEFAULT:''"`           //字段备注
	GsSimplComment string `json:"sSimplComment" gorm:"column:sSimplComment; type:varchar; DEFAULT:''"` //字段简洁备注(去除括号的内容)
	GiBigTxt       int    `json:"iBigTxt" gorm:"column:iBigTxt; type:varchar; DEFAULT:2"`              //是否大文本字段
}

// 表主键名称(首字母大写)
func (t *TableInfo) KeyName() string {
	for _, field := range t.Fields {
		if field.GiKey == 1 { // 主键
			return field.GsName
		}
	}

	return ""
}

// 表主键字段
func (t *TableInfo) KeyField() *Field {
	for _, field := range t.Fields {
		if field.GiKey == 1 { // 主键
			return field
		}
	}

	return nil
}

// 表主键字段集合(如果有多个主键,则全取,并以','分隔)
func (t *TableInfo) KeyFields() string {
	result := ""
	for _, field := range t.Fields {
		if field.GiKey == 1 { // 主键
			if result != "" {
				result = result + ","
			}
			result = result + field.GsName
		}
	}

	return result
}

// 主键长度
func (t *TableInfo) KeyLen() int {
	for _, field := range t.Fields {
		if field.GiKey == 1 { // 主键
			return field.GiMaxLength
		}
	}

	return -1
}

// 是否自增主键
func (t *TableInfo) IsAutoKey() bool {
	for _, field := range t.Fields {
		if field.GiAuto == 1 { // 主键
			return true
		}
	}

	return false
}

// 是否存在pid
func (t *TableInfo) HasPid() bool {
	for _, field := range t.Fields {
		if FrameworkField("sPid") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在path
func (t *TableInfo) HasPath() bool {
	for _, field := range t.Fields {
		if FrameworkField("sPath") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在recordKey
func (t *TableInfo) HasRecordKey() bool {
	for _, field := range t.Fields {
		if FrameworkField("sRecordKey") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在memo
func (t *TableInfo) HasMemo() bool {
	for _, field := range t.Fields {
		if FrameworkField("sMemo") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在creator
func (t *TableInfo) HasCreator() bool {
	for _, field := range t.Fields {
		if FrameworkField("sCreator") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在createDate
func (t *TableInfo) HasCreateDate() bool {
	for _, field := range t.Fields {
		if FrameworkField("dCreateDate") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在modifieder
func (t *TableInfo) HasModifieder() bool {
	for _, field := range t.Fields {
		if FrameworkField("sModifieder") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在modifiedDate
func (t *TableInfo) HasModifiedDate() bool {
	for _, field := range t.Fields {
		if FrameworkField("dModifiedDate") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在state
func (t *TableInfo) HasState() bool {
	for _, field := range t.Fields {
		if FrameworkField("iState") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在rank
func (t *TableInfo) HasIndex() bool {
	for _, field := range t.Fields {
		if FrameworkField("iIndex") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在edition
func (t *TableInfo) HasVersion() bool {
	for _, field := range t.Fields {
		if FrameworkField("iVersion") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在password
func (t *TableInfo) HasPassword() bool {
	for _, field := range t.Fields {
		if FrameworkField("sPassword") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在sign
func (t *TableInfo) HasSign() bool {
	for _, field := range t.Fields {
		if FrameworkField("iSign") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在onlyign
func (t *TableInfo) HasOnlyign() bool {
	for _, field := range t.Fields {
		if FrameworkField("iOnlyign") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在delSign
func (t *TableInfo) HasDelSign() bool {
	for _, field := range t.Fields {
		if FrameworkField("iDelSign") == field.GsName {
			return true
		}
	}

	return false
}

// 是否存在attached
func (t *TableInfo) HasAttached() bool {
	for _, field := range t.Fields {
		if FrameworkField("sAttached") == field.GsName {
			return true
		}
	}

	return false
}

// 大文本字段名称集合,';'分隔
func (t *TableInfo) GetBigTextFields() string {
	result := ""
	for _, field := range t.Fields {
		if field.GiBigTxt == 1 { // 大文本字段名称集合,';'分隔
			if result != "" {
				result = result + ","
			}
			result = result + field.GsName
		}
	}

	return result
}

/**
 * 取字段名称
 * @param map
 * @return
 */
func (t *TableInfo) GetFieldName(name string) string {
	if strings.TrimSpace(name) == "" {
		return ""
	}

	if len(t.Fields) < 1 {
		return ""
	}

	name = strings.TrimSpace(name)

	for _, field := range t.Fields {
		if field.GsName == name { // 直接按字段名找
			return field.GsName
		}
	}

	return ""
}

/**
 * 取字段名称
 * @param map
 * @return
 */
func (t *TableInfo) GetField(name string) *Field {
	if strings.TrimSpace(name) == "" {
		return nil
	}

	if len(t.Fields) < 1 {
		return nil
	}

	name = strings.TrimSpace(name)

	for _, field := range t.Fields {
		if field.GsName == name { // 直接按字段名找
			return field
		}
	}

	return nil
}

/**
 * 判断是否存在字段名
 * @param map
 * @return
 */
func (t *TableInfo) HasField(name string) bool {
	if strings.TrimSpace(name) == "" {
		return false
	}

	if len(t.Fields) < 1 {
		return false
	}

	name = strings.TrimSpace(name)

	for _, field := range t.Fields {
		if field.GsName == name { // 直接按字段名找
			return true
		}
	}

	return false
}

/**
 * 参考结构体排除指定属性名称的字段
 * data 数据
 * refer 检查用数据结构
 * removeNames 待移除的json名集合
 */
func (t *TableInfo) RemoveData(data map[string]interface{}, removeNames ...string) map[string]interface{} {
	filter := make(map[string]string, len(removeNames))
	for _, v := range removeNames {
		filter[v] = ""
	}

	result := map[string]interface{}{}
	for k, v := range data {
		if _, ok := filter[k]; ok {
			continue //符合待排除属性名称
		}
		result[k] = v
	}

	return result
}

/**
 * 对对象中不为null的属性检查限制
 * data 数据
 * ignoreNames 待忽略的字段
 */
func (t *TableInfo) ValidAttr(data map[string]interface{}, ignoreNames ...string) (int, error) {
	if data == nil {
		return 1001, t.createError("数据为nil")
	}

	if t == nil {
		return 1002, t.createError("数据结构为nil")
	}

	ignoreNamesMap := make(map[string]struct{}, len(ignoreNames))
	for _, v := range ignoreNames {
		ignoreNamesMap[v] = struct{}{}
	}

	for key, field := range t.Fields {
		if _, ok := ignoreNamesMap[key]; ok { //忽略字段
			continue
		}

		if _, ok := ignoreNamesMap[field.GsName]; ok { //忽略字段
			continue
		}

		if field.GiNull != 1 { //字段不允许为空
			temp := data[key]
			if temp == nil { //数据如果为空
				return 1004, t.createError(field.GsComment, "[", key, "]不允许为空！")
			} else if fmt.Sprintf("%v", temp) == "" {
				return 1005, t.createError(field.GsComment, "[", key, "]不允许为空！")
			}
		}

		oldValue := fmt.Sprintf("%v", data[key])
		strType := field.GsType

		if strType == "int" { // 此时oldValue的值为:"<int Value>"
			oldValue = fmt.Sprintf("%v", data[key])
		}

		iL := field.GiMaxLength //真正存储字段的最大长度要求不可能小于1,否则无意义,如果定义iMaxLength为0,或-1则说明是要忽略检查的
		if (!strings.Contains("/datetime/date/time/time.Time/decimal.Decimal", strType)) && (iL > 0) && (iL < utf8.RuneCountInString(oldValue)) {
			return 1006, t.createError(field.GsComment, "[", key, "]长度超出，最大长度限制为:", iL)
		}
	}

	return 1999, nil
}

/**
 * 对'编辑'对象中不为null的属性检查限制
 * data 数据
 * refer 检查用数据结构
 * ignoreNames 待忽略的字段
 */
func (t *TableInfo) ValidAttrByEdit(data map[string]interface{}, refer *TableInfo, ignoreNames ...string) (int, error) {
	if data == nil {
		return 1001, errors.New("数据为nil")
	}

	if refer == nil {
		return 1002, errors.New("数据结构为nil")
	}

	ignoreNamesMap := make(map[string]string, len(ignoreNames))
	for _, v := range ignoreNames {
		ignoreNamesMap[v] = v
	}

	for key, field := range refer.Fields {
		value, ok := data[key]
		if !ok {
			continue //此字段不参与更新则不进行检查
		}

		if _, ok = ignoreNamesMap[key]; ok {
			continue
		}

		if _, ok = ignoreNamesMap[field.GsName]; ok {
			continue
		}

		oldValue := strings.TrimSpace(fmt.Sprintf("%v", value))
		strType := field.GsType

		if strType == "int" { // 此时oldValue的值为:"<int Value>"
			oldValue = fmt.Sprintf("%v", value)
		}

		iL := field.GiMaxLength //真正存储字段的最大长度要求不可能小于1,否则无意义,如果定义iMaxLength为0,或-1则说明是要忽略检查的
		if (!strings.Contains("/datetime/date/time/time.Time/decimal.Decimal", strType)) && (iL > 0) && (iL < utf8.RuneCountInString(oldValue)) {
			return 1003, t.createError(field.GsComment, "[", key, "]长度超出，最大长度限制为:", iL)
		}

		if field.GiNull != 1 && (oldValue == "<nil>" || oldValue == "") {
			return 1004, t.createError(field.GsComment, "[", key, "]不允许为空！")
		}

		if strings.Contains("/decimal.Decimal/*big.Float", strType) && (iL > 0) {
			str := fmt.Sprintf("%v", data[key])
			array := strings.Split(str, ".")

			if field.GiIntLen < len(array[0]) {
				return 1005, t.createError(field.GsComment, "[", key, "]整数超出，最大长度限制为:", field.GiIntLen)
			}

			if (len(array) > 1) && (field.GiDecimalLen < len(array[1])) {
				return 1006, t.createError(field.GsComment, "[", key, "]小数超过，最大长度限制为:", field.GiIntLen)
			}
		}

		if strings.Contains("/float64/float32/", strType) && (iL > 0) {
			str := fmt.Sprintf("%f", data[key])
			array := strings.Split(str, ".")

			if field.GiIntLen < len(array[0]) {
				return 1005, t.createError(field.GsComment, "[", key, "]整数超出，最大长度限制为:", field.GiIntLen)
			}

			if (len(array) > 1) && (field.GiDecimalLen < len(array[1])) {
				return 1006, t.createError(field.GsComment, "[", key, "]小数超过，最大长度限制为:", field.GiIntLen)
			}
		}
	}

	return 1999, nil
}

/**
 * 对'新增'对象中属性检查限制
 * refer 检查用数据结构
 * ignoreNames 待忽略的字段
 */
func (t *TableInfo) ValidAttrByAdd(data map[string]interface{}, refer *TableInfo, ignoreNames ...string) (int, error) {
	if refer == nil {
		return 1001, t.createError("数据结构为nil")
	}

	ignoreNamesMap := make(map[string]string, len(ignoreNames))
	for _, v := range ignoreNames {
		ignoreNamesMap[v] = v
	}

	for key, field := range refer.Fields {
		if field.GiKey == 1 { //新增情况下不检查主键
			continue
		}

		if _, ok := ignoreNamesMap[key]; ok {
			continue
		}

		if _, ok := ignoreNamesMap[field.GsName]; ok {
			continue
		}

		if _, ok := data[key]; !ok && field.GiNull == 1 {
			continue
		}

		val := strings.TrimSpace(fmt.Sprintf("%v", data[key]))

		strType := fmt.Sprintf("%v", field.GsType) //类型

		if strType == "int" { // 此时oldValue的值为:"<int Value>"
			val = fmt.Sprintf("%v", data[key])
		}

		iL := field.GiMaxLength //真正存储字段的最大长度要求不可能小于1,否则无意义,如果定义iMaxLength为0,或-1则说明是要忽略检查的
		if !strings.Contains("/datetime/date/time/time.Time/decimal.Decimal/", strType) && iL > 0 && iL < utf8.RuneCountInString(val) {
			return 1002, t.createError(field.GsComment, "[", key, "]长度超出，最大长度限制为:", iL)
		}

		if field.GiNull != 1 && ((val == "<nil>") || (val == "") || (val == "null")) {
			return 1003, t.createError(field.GsComment, "[", key, "]不允许为空！")
		}

		if strings.Contains("/datetime/date/time/time.Time/", strType) && field.GiNull != 1 && (val == "" || val == "0001-01-01 00:00:00 +0000 UTC" || val == "<nil>") {
			return 1004, t.createError(field.GsComment, "[", key, "]不允许为空！")
		}

		if strings.Contains("/decimal.Decimal/*big.Float", strType) && (iL > 0) {
			str := fmt.Sprintf("%v", field)
			array := strings.Split(str, ".")

			if field.GiIntLen < len(array[0]) {
				return 1005, t.createError(field.GsComment, "[", key, "]整数超出，最大长度限制为:", field.GiIntLen)
			}

			if (len(array) > 1) && (field.GiDecimalLen < len(array[1])) {
				return 1006, t.createError(field.GsComment, "[", key, "]小数超过，最大长度限制为:", field.GiDecimalLen)
			}
		}

		if strings.Contains("/float64/float32/", strType) && (iL > 0) {
			str := fmt.Sprintf("%v", field)
			array := strings.Split(str, ".")

			if field.GiIntLen < len(array[0]) {
				return 1005, t.createError(field.GsComment, "[", key, "]整数超出，最大长度限制为:", field.GiIntLen)
			}

			if (len(array) > 1) && (field.GiDecimalLen < len(array[1])) {
				return 1006, t.createError(field.GsComment, "[", key, "]小数超过，最大长度限制为:", field.GiDecimalLen)
			}
		}
	}

	return 1999, nil
}

/**
 * 按实体保留map中的数据
 * refer 检查用数据结构
 * data 数据
 */
func (t *TableInfo) HoldByEntity(refer *TableInfo, data map[string]interface{}) map[string]interface{} {
	result := make(map[string]interface{}, 0)

	if refer == nil {
		return result
	}

	if data == nil {
		return result
	}

	for k, v := range data {
		if _, ok := refer.Fields[k]; ok {
			result[k] = v
			continue
		}

		name := refer.GetFieldName(k)
		if name != "" {
			result[k] = v
			continue
		}
	}

	return result
}

/**
 * 按实体筛选map中的数据,并返回字段名对应的数据
 * data 数据
 * refer 检查用数据结构
 * @return map[字段名]字段值
 */
func (t *TableInfo) FilterByEntity(data map[string]interface{}, refer *TableInfo) map[string]interface{} {
	result := make(map[string]interface{}, 0)

	if refer == nil {
		return result
	}

	if data == nil {
		return result
	}

	for k, v := range data {
		name := refer.GetFieldName(k)
		if name != "" {
			result[name] = v
			continue
		}
	}

	return result
}

/**
 * 创建错误信息
 * @param msg 错误信息
 * 返回错误信息
 */
func (t *TableInfo) createError(msg ...interface{}) error {
	var build strings.Builder
	for _, v := range msg {
		build.WriteString(fmt.Sprintf("%v", v))
	}

	return errors.New(build.String())
}

/**
 * 设置默认值
 * @param data
 * @param refer
 * @param cover 是否覆盖
 */
func (t *TableInfo) SetDefault(data map[string]interface{}, refer *TableInfo, cover bool) map[string]interface{} {
	for key, field := range refer.Fields {
		if field.GsDefaultData == "<nil>" {
			continue
		}

		if _, ok := data[key]; !ok {
			data[field.GsName] = field.GsDefaultData
		} else if cover {
			data[field.GsName] = field.GsDefaultData
		}
	}

	return data
}

/*
 * 创建记录验证值
 * @param data
 * @param recordKeyJam
 * @return
 */
func (t *TableInfo) CreateRecordKey(data map[string]interface{}, recordKeyJam string) string {
	temp := ""
	for _, field := range t.Columns { //只有数组才能固定顺序
		if field.GsName == FrameworkField("sRecordKey") { //recordKey字段不能参与,并且一旦轮询到recordKey就结束
			break
		}

		v, ok := data[field.GsName]
		if !ok {
			continue
		}

		if v == nil {
			temp = temp + ";"
			continue
		}

		switch reflect.TypeOf(v).String() {
		case "time.Time":
			temp = temp + v.(time.Time).Format(timeFormat) + ";"
		default:
			temp = temp + fmt.Sprintf("%v", v) + ";"
		}
	}

	temp = temp + ";" + recordKeyJam

	da := []byte(temp)
	md5Ctx := md5.New()
	md5Ctx.Write(da)
	cipherStr := md5Ctx.Sum(nil)
	result := hex.EncodeToString(cipherStr)

	return strings.ToUpper(result)
}

/*
 * 根据结构体创建map的记录验证值
 * @param refer 参照结构体,仅用于取结构
 * @param data 数据
 * @return
 */
func (t *TableInfo) CreateRecordKeyByMap(data map[string]interface{}, recordKeyJam string) string {
	temp := ""
	for _, field := range t.Columns { //只有数组才能固定顺序
		if field.GsName == FrameworkField("sRecordKey") { //recordKey字段不能参与,并且一旦轮询到recordKey就结束
			break
		}

		v, ok := data[field.GsName]
		if !ok {
			temp = temp + ";"
			continue
		}

		if v == nil {
			temp = temp + ";"
			continue
		}

		switch reflect.TypeOf(v).String() {
		case "time.Time":
			temp = temp + v.(time.Time).Format(timeFormat) + ";"
		default:
			temp = temp + fmt.Sprintf("%v", v) + ";"
		}
	}

	temp = temp + ";" + recordKeyJam

	b := []byte(temp)
	md5Ctx := md5.New()
	md5Ctx.Write(b)
	cipherStr := md5Ctx.Sum(nil)
	result := hex.EncodeToString(cipherStr)

	return strings.ToUpper(result)
}

// ReadFrameworkFieldInfo 取框架字段集合
func ReadFrameworkFieldInfo() map[string]string {
	frameworkFieldsOnce.Do(func() {
		root := ""
		exePath, err := os.Executable()
		if err != nil {
			root = "."
		}

		root, _ = filepath.EvalSymlinks(filepath.Dir(exePath))

		entityFilePath := filepath.Join(root, "config/system.ini")

		if _, err := os.Stat(entityFilePath); os.IsNotExist(err) {
			return
		}

		cfg, err := ini.Load(entityFilePath)
		if err != nil {
			return
		}

		keys, err := cfg.GetSection("FrameworkField")
		if err != nil {
			Log.Error("配置文件缺少配置组'FrameworkField'")
			return
		}

		frameworkFields = map[string]string{}
		for _, keyName := range keys.KeyStrings() {
			frameworkFields[keyName] = keys.Key(keyName).String()
		}
	})

	return frameworkFields
}

// FrameworkField 读取框架字段
func FrameworkField(name string) string {
	m := ReadFrameworkFieldInfo()
	return m[name]
}
